---
id: ipackage
title: 包序列化模式
---

### 定义

命名空间：TouchSocket.Core <br/>
程序集：[TouchSocket.Core.dll](https://www.nuget.org/packages/TouchSocket.Core)


## 一、说明
包序列化模式是为了解决**极限序列化**的问题。常规序列化的瓶颈，主要是反射、表达式树、创建对象等几个方面，这几个问题在运行时阶段，都没有一个好的解决方案。目前在net6以后，微软大力支持源代码生成，这使得这类问题得到了很大程度的解决。但是对于老项目，或者无法使用net6和vs2022以上的项目，是无法使用的。所以，这时候包序列化模式就显得非常需要了。

## 二、特点
【优点】

1. 简单、可靠、高效
2. 可以支持所有类型（需要自己编写代码）
3. 数据量最少（从理论来说这是占数据量最轻量的设计）

【缺点】

1. 要求序列化端和反序列化端必须保持一致，可以存在数据差异，但是不能出现数据断层。

## 三、使用
【实体类】
```csharp showLineNumbers
class MyClass : IPackage
{
    public int P1 { get; set; }
    public string P2 { get; set; }
    public char P3 { get; set; }
    public double P4 { get; set; }
    public List<int> P5 { get; set; }
    public Dictionary<int, MyClassModel> P6 { get; set; }
    public void Package(ByteBlock byteBlock)
    {
        //基础类型直接写入。
        byteBlock.Write(P1);
        byteBlock.Write(P2);
        byteBlock.Write(P3);
        byteBlock.Write(P4);

        //集合类型，可以先判断是否为null
        byteBlock.WriteIsNull(P5);
        if (P5 != null)
        {
            //如果不为null
            //就先写入集合长度
            //然后遍历将每个项写入
            byteBlock.Write(P5.Count);
            foreach (var item in P5)
            {
                byteBlock.Write(item);
            }
        }

        //字典类型，可以先判断是否为null
        byteBlock.WriteIsNull(P6);
        if (P6 != null)
        {
            //如果不为null
            //就先写入字典长度
            //然后遍历将每个项，按键、值写入
            byteBlock.Write(P6.Count);
            foreach (var item in P6)
            {
                byteBlock.Write(item.Key);
                byteBlock.WritePackage(item.Value);//因为值MyClassModel实现了IPackage，所以可以直接写入
            }
        }
    }

    public void Unpackage(ByteBlock byteBlock)
    {
        //基础类型按序读取。
        this.P1 = byteBlock.ReadInt32();
        this.P2 = byteBlock.ReadString();
        this.P3 = byteBlock.ReadChar();
        this.P4 = byteBlock.ReadDouble();

        var isNull = byteBlock.ReadIsNull();
        if (!isNull)
        {
            int count = byteBlock.ReadInt32();
            var list = new List<int>(count);
            for (int i = 0; i < count; i++)
            {
                list.Add(byteBlock.ReadInt32());
            }
            this.P5 = list;
        }


        isNull = byteBlock.ReadIsNull();//复用前面的变量，省的重新声明
        if (!isNull)
        {
            int count = byteBlock.ReadInt32();
            var dic = new Dictionary<int, MyClassModel>(count);
            for (int i = 0; i < count; i++)
            {
                dic.Add(byteBlock.ReadInt32(), byteBlock.ReadPackage<MyClassModel>());
            }
            this.P6 = dic;
        }
    }
}

class MyClassModel : PackageBase
{
    public DateTime P1 { get; set; }
    public override void Package(ByteBlock byteBlock)
    {
        byteBlock.Write(P1);
    }

    public override void Unpackage(ByteBlock byteBlock)
    {
        this.P1 = byteBlock.ReadDateTime();
    }
}
```
【打包和解包】
```csharp showLineNumbers
var myClass = new MyClass();
myClass.P1 = 1;
myClass.P2 = "若汝棋茗";
myClass.P3 = 'a';
myClass.P4= 3;

myClass.P5=new List<int> { 1, 2, 3 };

myClass.P6= new Dictionary<int, MyClassModel>() 
{
    { 1,new MyClassModel(){ P1=DateTime.Now} },
    { 2,new MyClassModel(){ P1=DateTime.Now} }
};

using (ByteBlock byteBlock=new ByteBlock())
{
    myClass.Package(byteBlock);//打包，相当于序列化

    byteBlock.Seek(0);//将流位置重置为0

    var myNewClass = new MyClass();
    myNewClass.Unpackage(byteBlock);//解包，相当于反序列化
}
```

## 四、使用源生成

如果源生成可用，使用源代码生成方式，可以实现**自动**的打包和解包。

例如上述类型，我们只需要使用`GeneratorPackage`特性标记即可。

```csharp {1} showLineNumbers
[GeneratorPackage]
internal partial class MyGeneratorPackage : PackageBase
{
    public int P1 { get; set; }
    public string P2 { get; set; }
    public char P3 { get; set; }
    public double P4 { get; set; }
    public List<int> P5 { get; set; }
    public Dictionary<int, MyClassModel> P6 { get; set; }
}
```

<details>
<summary>源生成的代码</summary>
<div>

```csharp showLineNumbers
/*
此代码由SourceGenerator工具直接生成，非必要请不要修改此处代码
*/
#pragma warning disable
using System;
using System.Diagnostics;
using TouchSocket.Core;
using System.Threading.Tasks;

namespace PackageConsoleApp
{
    [global::System.CodeDom.Compiler.GeneratedCode("TouchSocket.SourceGenerator", "2.0.0.0")]
    partial class MyGeneratorPackage
    {
        public override void Package(in ByteBlock byteBlock)
        {
            byteBlock.Write(P1);
            byteBlock.Write(P2);
            byteBlock.Write(P3);
            byteBlock.Write(P4);
            byteBlock.WriteIsNull(this.P5);
            if (this.P5 != null)
            {
                byteBlock.Write(this.P5.Count);
                foreach (var item in this.P5)
                {
                    byteBlock.Write(item);
                }
            }

            byteBlock.WriteIsNull(this.P6);
            if (this.P6 != null)
            {
                byteBlock.Write(this.P6.Count);
                foreach (var item in this.P6)
                {
                    byteBlock.Write(item.Key);
                    byteBlock.WritePackage(item.Value);
                }
            }
        }

        public override void Unpackage(in ByteBlock byteBlock)
        {
            P1 = byteBlock.ReadInt32();
            P2 = byteBlock.ReadString();
            P3 = byteBlock.ReadChar();
            P4 = byteBlock.ReadDouble();
            if (!byteBlock.ReadIsNull())
            {
                var len = byteBlock.ReadInt32();
                var list = new int[len];
                for (var i = 0; i < len; i++)
                {
                    list[i] = byteBlock.ReadInt32();
                }

                this.P5 = new System.Collections.Generic.List<int>(list);
            }

            if (!byteBlock.ReadIsNull())
            {
                var len = byteBlock.ReadInt32();
                var dic = new System.Collections.Generic.Dictionary<int, PackageConsoleApp.MyClassModel>(len);
                for (var i = 0; i < len; i++)
                {
                    var key = byteBlock.ReadInt32();
                    PackageConsoleApp.MyClassModel value = null;
                    if (!byteBlock.ReadIsNull())
                    {
                        value = new PackageConsoleApp.MyClassModel();
                        value.Unpackage(byteBlock);
                    }

                    dic.Add(key, value);
                }

                this.P6 = dic;
            }
        }
    }
}
```

</div>
</details>

:::caution 注意

使用源代码生成方式，需要确保包内的所有成员均为基础类型，或者`IPackage`。不然将无法生成。

同时，当包类型是结构体时，才可以直接实现`IPackage`接口。如果是实例类，则需要直接或间接使用`PackageBase`作为基类。

:::  

## 五、性能评测

基准测试表明：

包序列化模式和使用源代码生成方式工作的MemoryPack几乎一样。比json方式快了10倍多，比微软的json快了近4倍。

![](@site/static/img/docs/ipackage-1.png)
